/*
@hehung
2023-2-8
转载请注明出处，版权由@hehung所有
email: 1398660197@qq.com
wechat: hehung95
*/

#include "hal_data.h"
#include "app_uart.h"
#include <string.h>
#include "app_bl.h"
#include "data_flash_helper.h"
#include "app_flash_hex.h"
#include "app_led.h"
#include "app_key.h"


#define BL_OK                           (0U)
#define BL_NOT_OK                       (1U)

//no error, data is oK
#define BL_ERROR_CODE_0                 ("OK\r\n")
// error data checksum
#define BL_ERROR_CODE_1                 ("ERROR:1\r\n")
// error data lost
#define BL_ERROR_CODE_2                 ("ERROR:2\r\n")
// error for not start 
#define BL_ERROR_CODE_3                 ("ERROR:3\r\n")
// error start char in data line
#define BL_ERROR_CODE_9                 ("ERROR:9\r\n")

// timeout count
#define BL_TIMEOUT                      (5)    //ms

// (data length + address + type + 16 data + checksum )bytes every line
#define CODE_DATA_BUF_LEN               (32U)  // bytes


typedef int (*main_fnptr)(void); // Prototype for function pointer to the image



typedef struct
{
	uint8_t code_data_buf[CODE_DATA_BUF_LEN];
	uint8_t code_data_len;
	uint8_t code_step;
	uint32_t code_addr_base;
	uint32_t code_addr_offset;
} s_BlCodeDataType;



static uint32_t bl_timeout_cnt = 0U;
static s_BlCodeDataType code_data = 
{
	.code_data_len = 0,
	.code_step = 0,
	.code_addr_base = FLASH_CODE_AREA_START,
	.code_addr_offset = 0
};
static e_BlDownloadStaType bl_start_flag = BL_CODE_DOWNLOAD_IDLE;

session_header_t                    device_setup_session;


static void BL_BufClear(void);
static uint8_t Bl_Char2Hex(const char *data_ptr, uint8_t data_len);
static void Bl_HexFileTypeJudge(void);
static void Bl_DownloadRunning(void);
static void execute_user_code(uint32_t address);


static void execute_user_code(uint32_t address)
{
    main_fnptr *p_jump_to_app; // Function pointer main that will be used to jump to application

    __disable_irq();

#if 0       //Commented out 2022.06.24 WangJin
    /* Disable the stack monitor */
    R_MPU_SPMON->SP[0].PT = 0xA500U;                /* Enable access to Main Stack Pointer Monitor Access Control Register */
    R_MPU_SPMON->SP[1].PT = 0xA500U;                /* Enable access to Process Stack Pointer Monitor Access Control Register */
    R_MPU_SPMON->SP[0].CTL = 0U;                    /* disable the Main stack monitor */
    R_MPU_SPMON->SP[1].CTL = 0U;                    /* disable the Process stack monitor */
#else
    __set_MSPLIM(0);
#endif
    SysTick->CTRL  = 0;                             /* Disable the systick timer */
    NVIC_DisableIRQ ( SysTick_IRQn );               /* Disable the systick timer IRQ */
    NVIC_ClearPendingIRQ( SysTick_IRQn );           /* Clear any pending systick timer IRQ */

    R_USB_FS0->SYSCFG = 0;                            /* Disable USB FS */
    R_USB_FS0->DVSTCTR0 = 0;
    R_USB_FS0->INTENB0 = 0;
    R_USB_FS0->INTENB1 = 0;
    R_USB_FS0->INTSTS0 = 0;
    R_USB_FS0->INTSTS1 = 0;
    R_USB_FS0->BRDYENB = 0;
    R_USB_FS0->NRDYENB = 0;
    R_USB_FS0->BEMPENB =0;
    R_USB_FS0->USBADDR = 0;

    // Clear all interrupts!
    // ICU Event Link Setting Register i (IELSRi) (RA6M3: i = 0 to 95)
    for (uint32_t i = 0; i<96; i++)
    {
        R_ICU->IELSR[i] = 0;
    }

    for (uint32_t i = 0; i<8; i++)
    {
        NVIC->ICER[i] = 0xFFFFFFFF;
        NVIC->ICPR[i] = 0xFFFFFFFF;
    }

    // IRQ Control Register i (IRQCRi) (RA6M3: i = 0 to 15)
    for  (uint32_t i = 0; i<16; i++)
    {
        R_ICU->IRQCR[i] = 0;
    }

    p_jump_to_app = (main_fnptr*)(address+4);

    // SCB->VTOR = (address & 0x1FFFFF80);
	SCB->VTOR = (address);

    /* Complete all explicit memory accesses */
    __DSB();
    /* Flush the pipeline in the processor */
    __ISB();

    // Flush the ROM cache, or we won't see the changed data
    R_FCACHE->FCACHEE_b.FCACHEEN = 0;           //Disable Flash Memory Cache
    R_FCACHE->FCACHEIV_b.FCACHEIV = 1;          //Invalidate Flash Cache
    while (0 != R_FCACHE->FCACHEIV_b.FCACHEIV);
    R_FCACHE->FCACHEE_b.FCACHEEN = 1;           // Re-Enable ROM cache

    /* Set bits 20-23 to enable CP10 and CP11 coprocessor */
    SCB->CPACR = 0;

     /** Set stack here. */
    __set_MSP(*((uint32_t*)(address)));

    /* Ensure that when we jump we're using the Main Stack Pointer */
    __set_CONTROL(0);

    /* Clear lazy stacking for FPU operations */
    FPU->FPCCR &= 0xC0000000;

    /** Jump to image*/
     (*p_jump_to_app)();
     __NOP();
}


static void BL_BufClear(void)
{
	memset((char*)uart_buf.data_buf, '\0', sizeof(uart_buf.data_buf));
	uart_buf.data_idx = 0U;
}

static uint8_t Bl_Char2Hex(const char *data_ptr, uint8_t data_len)
{
	uint8_t i = 0;
	uint8_t temp_data[CODE_DATA_BUF_LEN << 1];
	uint16_t sum;
	uint8_t checksum;

	for (i = 0; i < data_len; i++)
	{
		// skip the start char and end char
		if ((data_ptr[i] >= 0x30) && (data_ptr[i] <= 0x39))
		{
			// char '0' ~ '9', change it to number
			temp_data[i] = data_ptr[i] - 0x30U;
		}
		else if ((data_ptr[i] >= 0x41) && (data_ptr[i] <= 0x46))
		{
			// char 'A'~'F', change it to number 10~15
			temp_data[i] = data_ptr[i] - 55;
		}
	}
	
	// convert the hex char to hex number
	code_data.code_data_len = 0;
	for (i = 0; i < data_len; i+=2)
	{
		code_data.code_data_buf[code_data.code_data_len ++] = (uint8_t)(temp_data[i] << 4U) + temp_data[i+1];
	}

	// checksum verification
	sum = 0;
	for (i = 0; i < code_data.code_data_len - 1; i++)
	{
		sum += code_data.code_data_buf[i];
	}
	
	checksum = (uint8_t)(0x100U - (sum % 256U));
	if (checksum == code_data.code_data_buf[code_data.code_data_len-1])
	{
		return BL_OK;
	}
	else
	{
		bl_start_flag = BL_CODE_DOWNLOAD_IDLE;
		return BL_NOT_OK;
	}
}

// 
static void Bl_HexFileTypeJudge(void)
{
	fsp_err_t  error = FSP_SUCCESS;
	
	// judge the data type
	if (code_data.code_data_buf[3] == 0x00)
	{
		if (BL_CODE_DOWNLOAD_IDLE == bl_start_flag)
		{
			FlashHex_StartProgramming();
			bl_start_flag = BL_CODE_DOWNLOAD_START;		
		}

		code_data.code_step = 0;
		// data record
		if (BL_CODE_DOWNLOAD_START == bl_start_flag)
		{
			// ensure this is in downloading status
			code_data.code_addr_offset = ((uint32_t)(code_data.code_data_buf[1] << 8U) |
											(uint32_t)(code_data.code_data_buf[2]));
		} 
		else
		{
			// not flag the start
			(void)Uart_SendData(BL_ERROR_CODE_2);
			bl_start_flag = BL_CODE_DOWNLOAD_IDLE;
		}
	}
	else if (code_data.code_data_buf[3] == 0x01)
	{
		// file end, 表示文件结束
		// start record
		bl_start_flag = BL_CODE_DOWNLOAD_IDLE;
		code_data.code_step = 1;

        /* erase persistent log */
        error = internal_storage_erase(PERSISTENT_LOG_METADATA_TYPE);
        if (FSP_SUCCESS != error)
        {
            Led_Ctrl(LED_1, 1);
        }

        device_setup_session.fw_image_size = 0;
        device_setup_session.is_fw_loaded = 1;
        device_setup_session.valid = 0xAA55;
        if (FSP_SUCCESS != internal_storage_write ((uint8_t *) &device_setup_session, sizeof(session_header_t), PERSISTENT_LOG_METADATA_TYPE))
        {
            Led_Ctrl(LED_1, 1);
        }

		internal_storage_deinit();
		// jump to application
		execute_user_code(FLASH_CODE_AREA_START);
	}
	else if (code_data.code_data_buf[3] == 0x04)
	{
		code_data.code_step = 4;
		// 扩展线性地址的记录
		if (BL_CODE_DOWNLOAD_IDLE == bl_start_flag)
		{
			FlashHex_StartProgramming();
			bl_start_flag = BL_CODE_DOWNLOAD_START;
		}
		
		// base address
		code_data.code_addr_base = (uint32_t)(((uint32_t)(code_data.code_data_buf[4] << 8U) |
						(uint32_t)(code_data.code_data_buf[5])) << 16U);
		// code_data.code_addr_base += FLASH_CODE_AREA_START;
	}
	else if (code_data.code_data_buf[3] == 0x05)
	{
		// 开始线性地址记录
		code_data.code_step = 5;
	}
}

// flash download 
static void Bl_DownloadRunning(void)
{
	uint32_t address;

	if (BL_CODE_DOWNLOAD_START == bl_start_flag)
	{
		if (code_data.code_step == 0U)
		{
			// calculate the address
			address = code_data.code_addr_base + code_data.code_addr_offset;
			if ((address >= FLASH_CODE_AREA_START) && (address <= FLASH_CODE_AREA_END))
			{
				write_flash(address, &code_data.code_data_buf[4], code_data.code_data_buf[0]);
			}
		}
	}
}

void Bl_Init(void)
{
	fsp_err_t  error = FSP_SUCCESS;
	
	// if sw1 or sw2 pressed, do not enter the application, waiting for downloading
	if (PIN_LOW == Key_ReadPinLevel(KEY_1) || (PIN_LOW == Key_ReadPinLevel(KEY_2)))
	{

	}
	else
	{
		/* Read the persistent device setup log from DATA FLASH */
		memset (&device_setup_session, 0, sizeof(device_setup_session));
		error = internal_storage_read ((uint8_t *) &device_setup_session, sizeof(session_header_t), PERSISTENT_LOG_METADATA_TYPE);
		if((FSP_SUCCESS != error) && (FSP_ERR_ABORTED != error))
		{
			Led_Ctrl(LED_1, 1);
		}

		if((device_setup_session.valid == 0xAA55) && (device_setup_session.is_fw_loaded == 1))
		{
			// jump to application
			execute_user_code(FLASH_CODE_AREA_START);
		}
	}
}


void Bl_RunningEntry(void)
{
	uint8_t err;

	if (uart_buf.data_idx > 0)
	{
		if (uart_buf.data_buf[0] == ':')
		{
			if (bl_timeout_cnt == 0U)
			{
				bl_timeout_cnt = 1U;    // start count
			}
			else if (bl_timeout_cnt >= BL_TIMEOUT)
			{
				// timeout, // not received a end char '\n'
				bl_timeout_cnt = 0U;
				// report error, lost data
				(void)Uart_SendData(BL_ERROR_CODE_2);
				// clear buffer
				BL_BufClear();
			}
			

			if (uart_buf.data_buf[uart_buf.data_idx - 1] == '\n')
			{
				bl_timeout_cnt = 0U;

				// process these data
				err = Bl_Char2Hex((char*)&uart_buf.data_buf[1], uart_buf.data_idx-2);
				if (err == BL_OK)
				{
					Bl_HexFileTypeJudge();
					Bl_DownloadRunning();
					memset(code_data.code_data_buf, 0xFF, CODE_DATA_BUF_LEN);
					// feedback
					(void)Uart_SendData(BL_ERROR_CODE_0);
					// (void)Uart_SendData((char*)uart_buf.data_buf);
					// (void)Uart_SendData((char*)code_data.code_data_buf);
				}
				else
				{
					// error, checksum error
					(void)Uart_SendData(BL_ERROR_CODE_1);
				}

				
				// clear buffer
				BL_BufClear();				
			}
		}
		else
		{
			// error
			(void)Uart_SendData(BL_ERROR_CODE_9);
			(void)Uart_SendData((char*)uart_buf.data_buf);
			// clear buffer
			BL_BufClear();	
		}
	}
}


// 1ms callback function
void Bl_TimeoutLgc(void)
{
	if (bl_timeout_cnt != 0U)
	{
		bl_timeout_cnt ++;
	}
}

e_BlDownloadStaType Bl_GetDownloadStatus(void)
{
	return bl_start_flag;
}

